package com.leonxtp.library;

import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.StateListDrawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.content.ContextCompat;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.SoundEffectConstants;
import android.view.ViewConfiguration;
import android.view.ViewParent;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.CompoundButton;


/**
 * SwitchButton
 *
 * @author leonxtp
 * @since 2017-12-29
 * <p>
 * 一个简化版的switch button
 * <p>
 * 属性:
 * int:
 * width, height, thumbWidth=thumbHeight, backWidth, backHeight, thumbMargin, textMargin
 * <p>
 * float:
 * thumbRangeRatio
 * <p>
 * ColorStateList:
 * backColor, thumbColor
 * <p>
 * 绘制逻辑：
 * thumbRangeRatio 和 文本 不能同时生效，以文本优先
 * thumb固定都是圆形
 * thumb都在内部不超出back
 * 如果有文本，文本处于back边缘和thumb的居中位置，不考虑文本过长的问题，开发者自己注意控制，实际上不会超出
 * ===============================================================
 * 宽高都指定：必须满足宽度 > 高度，否则拒绝绘制!
 * backWidth =  width - padding
 * thumbWidth = min(width,height) - padding - thumbMargin*2
 * ===============================================================
 * 宽度不定，高度指定：
 * thumbWidth = height - padding - thumbMargin*2
 * 有文本：backWidth = thumbWidth + thumbMargin + textWidth + textMargin*2
 * 无文本：backWidth = thumbWidth*thumbRangeRatio + thumbMargin*2
 * ===============================================================
 * 宽高都不指定，将根据thumbWidth 和 （thumbRangeRatio 或 textWidth ）确定
 * thumbWidth = 默认宽度或者指定宽度
 * backWidth：同高度指定
 */

@SuppressWarnings("unused")
public class MySwitchButton extends CompoundButton {
    public static final float DEFAULT_THUMB_RANGE_RATIO = 1.8f;

    public static final int DEFAULT_THUMB_SIZE_DP = 20;
    public static final int DEFAULT_THUMB_MARGIN_DP = 2;
    public static final int DEFAULT_ANIMATION_DURATION = 250;
    public static final int DEFAULT_TINT_COLOR = 0x327FC2;

    private static int[] CHECKED_PRESSED_STATE = new int[]{android.R.attr.state_checked,
            android.R.attr.state_enabled, android.R.attr.state_pressed};
    private static int[] UNCHECKED_PRESSED_STATE = new int[]{-android.R.attr.state_checked,
            android.R.attr.state_enabled, android.R.attr.state_pressed};

    private ColorStateList mBackColor, mThumbColor;
    private float mThumbRadius, mBackRadius;
    /**
     * 用这种方式来保存四个margin值， 我也是醉了
     */
    private int mThumbMargin;
    private float mThumbRangeRatio;
    private long mAnimationDuration;
    private int mThumbWidth;
    private int mThumbHeight;
    private int mBackWidth;
    private int mBackHeight;

    private boolean mFadeBack;

    /**
     * IMPORTANT
     */
    private int mCurrThumbColor;
    private int mCurrBackColor, mNextBackColor;
    private int mOnTextColor, mOffTextColor;
    // IMPORTANT
    private RectF mThumbRectF, mBackRectF, mSafeRectF, mTextOnRectF, mTextOffRectF;
    private Paint mPaint;
    // whether using Drawable for thumb or back
    private boolean mIsThumbUseDrawable, mIsBackUseDrawable;
    private boolean mDrawDebugRect = false;
    private ObjectAnimator mProgressAnimator;
    // animation control
    private float mProgress;
    // temp position of thumb when dragging or animating
    private RectF mPresentThumbRectF;
    private float mStartX, mStartY, mLastX;
    private int mTouchSlop;
    private int mClickTimeout;
    private Paint mRectPaint;
    private CharSequence mTextOn;
    private CharSequence mTextOff;
    private TextPaint mTextPaint;
    private Layout mOnLayout;
    private Layout mOffLayout;
    private float mTextWidth;
    private float mTextHeight;
    private int mTextExtra;
    // FIX #78,#85 : When restoring saved states, setChecked() called by super. So disable
    // animation and event listening when restoring.
    private boolean mRestoring = false;
    private boolean mReady = false;
    private boolean mCatch = false;

    private OnCheckedChangeListener mChildOnCheckedChangeListener;

    public MySwitchButton(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(attrs);
    }

    public MySwitchButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs);
    }

    public MySwitchButton(Context context) {
        super(context);
        init(null);
    }

    private void init(AttributeSet attrs) {
        mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
        // IMPORTANT
        mClickTimeout = ViewConfiguration.getPressedStateDuration() + ViewConfiguration.getTapTimeout();

        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mRectPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mRectPaint.setStyle(Paint.Style.STROKE);
        mRectPaint.setStrokeWidth(getResources().getDisplayMetrics().density);

        mTextPaint = getPaint();

        mThumbRectF = new RectF();
        mBackRectF = new RectF();
        mSafeRectF = new RectF();
        mTextOnRectF = new RectF();
        mTextOffRectF = new RectF();

        mProgressAnimator = ObjectAnimator.ofFloat(this, "progress", 0, 0).setDuration(DEFAULT_ANIMATION_DURATION);
        mProgressAnimator.setInterpolator(new AccelerateDecelerateInterpolator());

        mPresentThumbRectF = new RectF();

        Resources res = getResources();
        float density = res.getDisplayMetrics().density;

        Drawable thumbDrawable = null;
        ColorStateList thumbColor = null;
        float margin = density * DEFAULT_THUMB_MARGIN_DP;
        float thumbRangeRatio = 0;
        float marginLeft = 0;
        float marginRight = 0;
        float marginTop = 0;
        float marginBottom = 0;
        float thumbWidth = 0;
        float thumbHeight = 0;
        float thumbRadius = -1;
        float backRadius = -1;
        Drawable backDrawable = null;
        ColorStateList backColor = null;
        int animationDuration = DEFAULT_ANIMATION_DURATION;
        boolean fadeBack = true;
        int tintColor = 0;
        String textOn = null;
        String textOff = null;
        int textThumbInset = 0;
        int textExtra = 0;
        int textAdjust = 0;

        TypedArray ta = attrs == null ? null : getContext().obtainStyledAttributes(attrs, R.styleable.SwitchButton);
        if (ta != null) {
            thumbDrawable = ta.getDrawable(R.styleable.SwitchButton_kswThumbDrawable);
            thumbColor = ta.getColorStateList(R.styleable.SwitchButton_kswThumbColor);
            margin = ta.getDimension(R.styleable.SwitchButton_kswThumbMargin, margin);
            thumbRangeRatio = ta.getFloat(R.styleable.SwitchButton_kswThumbRangeRatio, thumbRangeRatio);
            thumbWidth = ta.getDimension(R.styleable.SwitchButton_kswThumbWidth, thumbWidth);
            thumbHeight = ta.getDimension(R.styleable.SwitchButton_kswThumbHeight, thumbHeight);
            thumbRadius = ta.getDimension(R.styleable.SwitchButton_kswThumbRadius, thumbRadius);
            backRadius = ta.getDimension(R.styleable.SwitchButton_kswBackRadius, backRadius);
            backDrawable = ta.getDrawable(R.styleable.SwitchButton_kswBackDrawable);
            backColor = ta.getColorStateList(R.styleable.SwitchButton_kswBackColor);
            animationDuration = ta.getInteger(R.styleable.SwitchButton_kswAnimationDuration, animationDuration);
            fadeBack = ta.getBoolean(R.styleable.SwitchButton_kswFadeBack, true);
            tintColor = ta.getColor(R.styleable.SwitchButton_kswTintColor, tintColor);
            textOn = ta.getString(R.styleable.SwitchButton_kswTextOn);
            textOff = ta.getString(R.styleable.SwitchButton_kswTextOff);
            textThumbInset = ta.getDimensionPixelSize(R.styleable.SwitchButton_kswTextThumbInset, 0);
            textExtra = ta.getDimensionPixelSize(R.styleable.SwitchButton_kswTextExtra, 0);
            textAdjust = ta.getDimensionPixelSize(R.styleable.SwitchButton_kswTextAdjust, 0);
            ta.recycle();
        }

        // click
        ta = attrs == null ? null : getContext().obtainStyledAttributes(attrs,
                new int[]{android.R.attr.focusable, android.R.attr.clickable});
        if (ta != null) {
            boolean focusable = ta.getBoolean(0, true);
            //noinspection ResourceType
            @SuppressLint("ResourceType")
            boolean clickable = ta.getBoolean(1, focusable);
            setFocusable(focusable);
            setClickable(clickable);
            ta.recycle();
        } else {
            setFocusable(true);
            setClickable(true);
        }

        mThumbMargin = (int) margin;
        mThumbRangeRatio = thumbRangeRatio <= 1 ? DEFAULT_THUMB_RANGE_RATIO : thumbRangeRatio;
        mFadeBack = fadeBack;

        // text
        mTextOn = textOn;
        mTextOff = textOff;
        mTextExtra = textExtra;

        mThumbColor = thumbColor;

        if (!mIsThumbUseDrawable && mThumbColor == null) {
            mThumbColor = ColorUtils.generateThumbColorWithTintColor(0xececec);
            mCurrThumbColor = mThumbColor.getDefaultColor();
        }

        // thumbSize
        mThumbWidth = ceil(thumbWidth);
        mThumbHeight = ceil(thumbHeight);

        // back drawable and color
        //ColorStateList
        mBackColor = backColor;
        if (!mIsBackUseDrawable && mBackColor == null) {
            mBackColor = ColorUtils.generateBackColorWithTintColor(0xececec);
            mCurrBackColor = mBackColor.getDefaultColor();
            mNextBackColor = mBackColor.getColorForState(CHECKED_PRESSED_STATE, mCurrBackColor);
        }

        mThumbRadius = thumbRadius;
        mBackRadius = backRadius;
        mAnimationDuration = animationDuration;

        mProgressAnimator.setDuration(mAnimationDuration);

        // sync checked status
        if (isChecked()) {
            setProgress(1);
        }
    }

    private CharSequence mTextOnNew, mTextOffNew;

    public void updateTextOn(CharSequence textOn, boolean immediately) {
        this.mTextOnNew = textOn;
        mReady = false;
        if (immediately) {
            requestLayout();
            invalidate();
        }
    }

    public void updateTextOff(CharSequence textOff, boolean immediately) {
        this.mTextOffNew = textOff;
        mReady = false;
        if (immediately) {
            requestLayout();
            invalidate();
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        /*
         * ensure textLayout
         */
        if ((mOnLayout == null && !TextUtils.isEmpty(mTextOn) || !TextUtils.isEmpty(mTextOnNew) && !mTextOnNew.equals(mTextOn))) {
            mTextOn = TextUtils.isEmpty(mTextOnNew) ? mTextOn : mTextOnNew;
            mOnLayout = makeLayout(mTextOn);
        }
        if ((mOffLayout == null && !TextUtils.isEmpty(mTextOff) || !TextUtils.isEmpty(mTextOffNew) && !mTextOffNew.equals(mTextOff))) {
            mTextOff = TextUtils.isEmpty(mTextOffNew) ? mTextOff : mTextOffNew;
            mOffLayout = makeLayout(mTextOff);
        }


        float onWidth = mOnLayout != null ? mOnLayout.getWidth() : 0;
        float offWidth = mOffLayout != null ? mOffLayout.getWidth() : 0;
        if (onWidth != 0 || offWidth != 0) {
            mTextWidth = Math.max(onWidth, offWidth);
        } else {
            mTextWidth = 0;
        }

        float onHeight = mOnLayout != null ? mOnLayout.getHeight() : 0;
        float offHeight = mOffLayout != null ? mOffLayout.getHeight() : 0;
        if (onHeight != 0 || offHeight != 0) {
            mTextHeight = Math.max(onHeight, offHeight);
        } else {
            mTextHeight = 0;
        }

        Log.e("onMeasure", "textWH:" + mTextWidth + ", " + mTextHeight);

        int[] measured = measureWidthHeight(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(measured[0], measured[1]);
    }

    private Layout makeLayout(CharSequence text) {
        return new StaticLayout(text, mTextPaint, (int) Math.ceil(Layout.getDesiredWidth(text, mTextPaint)),
                Layout.Alignment.ALIGN_CENTER, 1.f, 0, false);
    }

    private int[] measureWidthHeight(int widthMeasureSpec, int heightMeasureSpec) {

        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        int[] measuredResult = new int[2];

        if (widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY) {
            //宽高都指定

            if (heightSize > widthSize) {
                throw new IllegalArgumentException("Width must be larger than height!");
            }

            mThumbWidth = heightSize - getPaddingTop() - getPaddingBottom() - mThumbMargin * 2;

            int minWidthNeeded = mThumbMargin + mThumbWidth + mTextExtra * 2 + (int) mTextWidth;

            if (minWidthNeeded > widthSize) {
                throw new IllegalArgumentException("Width must be larger after text is shown!");
            }

            mBackWidth = widthSize - getPaddingLeft() - getPaddingRight();
            mBackHeight = heightSize - getPaddingTop() - getPaddingBottom();
            mThumbRangeRatio = (mBackWidth - mThumbMargin * 2.0f) / (mThumbWidth);

            measuredResult[0] = widthSize;
            measuredResult[1] = heightSize;

        } else {

            if (heightMode == MeasureSpec.EXACTLY) {
                //只指定高度
                mThumbWidth = heightSize - getPaddingTop() - getPaddingBottom() - mThumbMargin * 2;
            } else {
                mThumbWidth = ceil(getResources().getDisplayMetrics().density * DEFAULT_THUMB_SIZE_DP);
            }

            mBackHeight = mThumbWidth + mThumbMargin * 2 + getPaddingTop() + getPaddingBottom();

            if (mTextWidth != 0) {
                mBackWidth = mThumbMargin + mThumbWidth + mTextExtra * 2 + (int) mTextWidth;
                mThumbRangeRatio = (mBackWidth - mThumbMargin * 2.0f) / (mThumbWidth);

            } else {
                mBackWidth = (int) (mThumbWidth * mThumbRangeRatio) + mThumbMargin * 2;
            }

            measuredResult[0] = mBackWidth + getPaddingLeft() + getPaddingRight();
            measuredResult[1] = mThumbWidth + mThumbMargin * 2 + getPaddingTop() + getPaddingBottom();
        }

        mThumbHeight = mThumbWidth;

        return measuredResult;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if (w != oldw || h != oldh) {
            setup();
        }
    }

    private int ceil(double dimen) {
        return (int) Math.ceil(dimen);
    }

    /**
     * set up the rect of back and thumb
     */
    private void setup() {
        if (mThumbWidth == 0 || mThumbHeight == 0 || mBackWidth == 0 || mBackHeight == 0) {
            return;
        }

        if (mThumbRadius == -1) {
            mThumbRadius = Math.min(mThumbWidth, mThumbHeight) / 2;
        }
        if (mBackRadius == -1) {
            mBackRadius = Math.min(mBackWidth, mBackHeight) / 2;
        }

        int contentWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
        int contentHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();

        // max range of drawing content, when thumbMargin is negative, drawing range is larger than backWidth
        int drawingWidth = mBackWidth;
        int drawingHeight = mBackHeight;

        float thumbTop;
        // center vertical in content area
        thumbTop = getPaddingTop() + Math.max(0, mThumbMargin);

        float thumbLeft;
        thumbLeft = getPaddingLeft() + Math.max(0, mThumbMargin);

        mThumbRectF.set(thumbLeft, thumbTop, thumbLeft + mThumbWidth, thumbTop + mThumbHeight);

        float backLeft = mThumbRectF.left - mThumbMargin;
        // TODO
        mBackRectF.set(backLeft,
                mThumbRectF.top - mThumbMargin,
                backLeft + mBackWidth,
                mThumbRectF.top - mThumbMargin + mBackHeight);

        // TODO
        mSafeRectF.set(mThumbRectF.left, 0, mBackRectF.right - mThumbMargin - mThumbRectF.width(), 0);

        float minBackRadius = Math.min(mBackRectF.width(), mBackRectF.height()) / 2.f;
        mBackRadius = Math.min(minBackRadius, mBackRadius);

        //TODO
        if (mOnLayout != null) {
            //GOOD
            float onLeft = mBackRectF.left
                    + (mBackRectF.width() - mThumbWidth - mThumbMargin - mOnLayout.getWidth()) / 2f;
            float onTop = mBackRectF.top
                    + (mBackRectF.height() - mOnLayout.getHeight()) / 2;
            mTextOnRectF.set(onLeft, onTop,
                    onLeft + mOnLayout.getWidth(), onTop + mOnLayout.getHeight());
        }

        if (mOffLayout != null) {
            float offLeft = mBackRectF.left + mThumbMargin + mThumbWidth
                    + (mBackRectF.width() - mThumbWidth - mThumbMargin - mOffLayout.getWidth()) / 2f;
//            float offLeft = mBackRectF.right
//                    - (mBackRectF.width() - mThumbWidth - mThumbMargin - mOffLayout.getWidth()) / 2f
//                    - mOffLayout.getWidth();
            float offTop = mBackRectF.top + (mBackRectF.height() - mOffLayout.getHeight()) / 2;
            mTextOffRectF.set(offLeft, offTop, offLeft + mOffLayout.getWidth(), offTop + mOffLayout.getHeight());
        }

        mReady = true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (!mReady) {
            setup();
        }
        if (!mReady) {
            return;
        }


        if (mFadeBack) {
            int alpha;
            int colorAlpha;

            // fix #75
            int belowColor = isChecked() ? mCurrBackColor : mNextBackColor;
            int aboveColor = isChecked() ? mNextBackColor : mCurrBackColor;

            // curr back
            alpha = (int) (255 * getProgress());
            colorAlpha = Color.alpha(belowColor);
            colorAlpha = colorAlpha * alpha / 255;
            mPaint.setARGB(colorAlpha, Color.red(belowColor), Color.green(belowColor), Color.blue(belowColor));
            canvas.drawRoundRect(mBackRectF, mBackRadius, mBackRadius, mPaint);

            // next back
            alpha = 255 - alpha;
            colorAlpha = Color.alpha(aboveColor);
            colorAlpha = colorAlpha * alpha / 255;
            mPaint.setARGB(colorAlpha, Color.red(aboveColor), Color.green(aboveColor), Color.blue(aboveColor));
            canvas.drawRoundRect(mBackRectF, mBackRadius, mBackRadius, mPaint);

            mPaint.setAlpha(255);
        } else {
            mPaint.setColor(mCurrBackColor);
            canvas.drawRoundRect(mBackRectF, mBackRadius, mBackRadius, mPaint);
        }


        // text
        Layout switchText = getProgress() > 0.5 ? mOnLayout : mOffLayout;
        RectF textRectF = getProgress() > 0.5 ? mTextOnRectF : mTextOffRectF;
        if (switchText != null && textRectF != null) {
            int alpha = (int) (255 * (getProgress() >= 0.75 ? getProgress() * 4 - 3 : (getProgress() < 0.25 ? 1 - getProgress() * 4 : 0)));
            int textColor = getProgress() > 0.5 ? mOnTextColor : mOffTextColor;
            int colorAlpha = Color.alpha(textColor);
            colorAlpha = colorAlpha * alpha / 255;
            switchText.getPaint().setARGB(colorAlpha, Color.red(textColor), Color.green(textColor), Color.blue(textColor));
            canvas.save();
            canvas.translate(textRectF.left, textRectF.top);
            switchText.draw(canvas);
            canvas.restore();
        }

        // thumb
        mPresentThumbRectF.set(mThumbRectF);
        mPresentThumbRectF.offset(mProgress * mSafeRectF.width(), 0);

        mPaint.setColor(mCurrThumbColor);
        canvas.drawRoundRect(mPresentThumbRectF, mThumbRadius, mThumbRadius, mPaint);

        if (mDrawDebugRect) {
            mRectPaint.setColor(Color.parseColor("#AA0000"));
            canvas.drawRect(mBackRectF, mRectPaint);
            mRectPaint.setColor(Color.parseColor("#0000FF"));
            canvas.drawRect(mPresentThumbRectF, mRectPaint);
            mRectPaint.setColor(Color.parseColor("#000000"));
            canvas.drawLine(mSafeRectF.left, mThumbRectF.top, mSafeRectF.right, mThumbRectF.top, mRectPaint);
            mRectPaint.setColor(Color.parseColor("#00CC00"));
            canvas.drawRect(getProgress() > 0.5 ? mTextOnRectF : mTextOffRectF, mRectPaint);
        }
    }

    @Override
    protected void drawableStateChanged() {
        super.drawableStateChanged();

        if (!mIsThumbUseDrawable && mThumbColor != null) {
            mCurrThumbColor = mThumbColor.getColorForState(getDrawableState(), mCurrThumbColor);
        }

        int[] nextState = isChecked() ? UNCHECKED_PRESSED_STATE : CHECKED_PRESSED_STATE;
        ColorStateList textColors = getTextColors();
        if (textColors != null) {
            int defaultTextColor = textColors.getDefaultColor();
            mOnTextColor = textColors.getColorForState(CHECKED_PRESSED_STATE, defaultTextColor);
            mOffTextColor = textColors.getColorForState(UNCHECKED_PRESSED_STATE, defaultTextColor);
        }
        if (!mIsBackUseDrawable && mBackColor != null) {
            mCurrBackColor = mBackColor.getColorForState(getDrawableState(), mCurrBackColor);
            mNextBackColor = mBackColor.getColorForState(nextState, mCurrBackColor);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        if (!isEnabled() || !isClickable() || !isFocusable() || !mReady) {
            return false;
        }

        int action = event.getAction();

        float deltaX = event.getX() - mStartX;
        float deltaY = event.getY() - mStartY;

        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mStartX = event.getX();
                mStartY = event.getY();
                mLastX = mStartX;
                setPressed(true);
                break;

            case MotionEvent.ACTION_MOVE:
                float x = event.getX();
                setProgress(getProgress() + (x - mLastX) / mSafeRectF.width());
                if (!mCatch && (Math.abs(deltaX) > mTouchSlop / 2 || Math.abs(deltaY) > mTouchSlop / 2)) {
                    if (deltaY == 0 || Math.abs(deltaX) > Math.abs(deltaY)) {
                        catchView();
                    } else if (Math.abs(deltaY) > Math.abs(deltaX)) {
                        return false;
                    }
                }
                mLastX = x;
                break;

            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                mCatch = false;
                setPressed(false);
                float time = event.getEventTime() - event.getDownTime();
                if (Math.abs(deltaX) < mTouchSlop && Math.abs(deltaY) < mTouchSlop && time < mClickTimeout) {
                    performClick();
                } else {
                    boolean nextStatus = getStatusBasedOnPos();
                    if (nextStatus != isChecked()) {
                        playSoundEffect(SoundEffectConstants.CLICK);
                        setChecked(nextStatus);
                    } else {
                        animateToState(nextStatus);
                    }
                }
                break;

            default:
                break;
        }
        return true;
    }


    /**
     * return the status based on position of thumb
     *
     * @return whether checked or not
     */
    private boolean getStatusBasedOnPos() {
        return getProgress() > 0.5f;
    }

    private float getProgress() {
        return mProgress;
    }

    private void setProgress(final float progress) {
        float tp = progress;
        if (tp > 1) {
            tp = 1;
        } else if (tp < 0) {
            tp = 0;
        }
        this.mProgress = tp;
        invalidate();
    }

    /**
     * processing animation
     *
     * @param checked checked or unChecked
     */
    protected void animateToState(boolean checked) {
        if (mProgressAnimator == null) {
            return;
        }
        if (mProgressAnimator.isRunning()) {
            mProgressAnimator.cancel();
        }
        mProgressAnimator.setDuration(mAnimationDuration);
        if (checked) {
            mProgressAnimator.setFloatValues(mProgress, 1f);
        } else {
            mProgressAnimator.setFloatValues(mProgress, 0);
        }
        mProgressAnimator.start();
    }

    private void catchView() {
        ViewParent parent = getParent();
        if (parent != null) {
            parent.requestDisallowInterceptTouchEvent(true);
        }
        mCatch = true;
    }

    @Override
    public void setChecked(final boolean checked) {
        // animate before super.setChecked() because user may call setChecked again in OnCheckedChangedListener
        if (isChecked() != checked) {
            animateToState(checked);
        }
        if (mRestoring) {
            setCheckedImmediatelyNoEvent(checked);
        } else {
            super.setChecked(checked);
        }
    }

    public void setCheckedNoEvent(final boolean checked) {
        if (mChildOnCheckedChangeListener == null) {
            setChecked(checked);
        } else {
            super.setOnCheckedChangeListener(null);
            setChecked(checked);
            super.setOnCheckedChangeListener(mChildOnCheckedChangeListener);
        }
    }

    public void setCheckedImmediatelyNoEvent(boolean checked) {
        if (mChildOnCheckedChangeListener == null) {
            setCheckedImmediately(checked);
        } else {
            super.setOnCheckedChangeListener(null);
            setCheckedImmediately(checked);
            super.setOnCheckedChangeListener(mChildOnCheckedChangeListener);
        }
    }

    public void toggleNoEvent() {
        if (mChildOnCheckedChangeListener == null) {
            toggle();
        } else {
            super.setOnCheckedChangeListener(null);
            toggle();
            super.setOnCheckedChangeListener(mChildOnCheckedChangeListener);
        }
    }

    public void toggleImmediatelyNoEvent() {
        if (mChildOnCheckedChangeListener == null) {
            toggleImmediately();
        } else {
            super.setOnCheckedChangeListener(null);
            toggleImmediately();
            super.setOnCheckedChangeListener(mChildOnCheckedChangeListener);
        }
    }

    @Override
    public void setOnCheckedChangeListener(OnCheckedChangeListener onCheckedChangeListener) {
        super.setOnCheckedChangeListener(onCheckedChangeListener);
        mChildOnCheckedChangeListener = onCheckedChangeListener;
    }

    public void setCheckedImmediately(boolean checked) {
        super.setChecked(checked);
        if (mProgressAnimator != null && mProgressAnimator.isRunning()) {
            mProgressAnimator.cancel();
        }
        setProgress(checked ? 1 : 0);
        invalidate();
    }

    public void toggleImmediately() {
        setCheckedImmediately(!isChecked());
    }

    private void setDrawableState(Drawable drawable) {
        if (drawable != null) {
            int[] myDrawableState = getDrawableState();
            drawable.setState(myDrawableState);
            invalidate();
        }
    }

    public boolean isDrawDebugRect() {
        return mDrawDebugRect;
    }

    public void setDrawDebugRect(boolean drawDebugRect) {
        mDrawDebugRect = drawDebugRect;
        invalidate();
    }

    public long getAnimationDuration() {
        return mAnimationDuration;
    }

    public void setAnimationDuration(long animationDuration) {
        mAnimationDuration = animationDuration;
    }

    public float getThumbRangeRatio() {
        return mThumbRangeRatio;
    }

    public void setThumbRangeRatio(float thumbRangeRatio) {
        mThumbRangeRatio = thumbRangeRatio;
        // We need to mark "ready" to false since requestLayout may not cause size changed.
        mReady = false;
        requestLayout();
    }

    public float getThumbWidth() {
        return mThumbWidth;
    }

    public float getThumbHeight() {
        return mThumbHeight;
    }

    public float getThumbRadius() {
        return mThumbRadius;
    }

    public void setThumbRadius(float thumbRadius) {
        mThumbRadius = thumbRadius;
        if (!mIsThumbUseDrawable) {
            invalidate();
        }
    }

    public PointF getBackSizeF() {
        return new PointF(mBackRectF.width(), mBackRectF.height());
    }

    public float getBackRadius() {
        return mBackRadius;
    }

    public void setBackRadius(float backRadius) {
        mBackRadius = backRadius;
        if (!mIsBackUseDrawable) {
            invalidate();
        }
    }

    public boolean isFadeBack() {
        return mFadeBack;
    }

    public void setFadeBack(boolean fadeBack) {
        mFadeBack = fadeBack;
    }

    public void setText(CharSequence onText, CharSequence offText) {
        mTextOn = onText;
        mTextOff = offText;

        mOnLayout = null;
        mOffLayout = null;

        mReady = false;
        requestLayout();
        invalidate();
    }

    public CharSequence getTextOn() {
        return mTextOn;
    }

    public CharSequence getTextOff() {
        return mTextOff;
    }

    public void setTextExtra(int textExtra) {
        mTextExtra = textExtra;
        mReady = false;
        requestLayout();
        invalidate();
    }

    @Override
    public Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        SavedState ss = new SavedState(superState);
        ss.onText = mTextOn;
        ss.offText = mTextOff;
        return ss;
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        SavedState ss = (SavedState) state;
        setText(ss.onText, ss.offText);
        mRestoring = true;
        super.onRestoreInstanceState(ss.getSuperState());
        mRestoring = false;
    }

    static class SavedState extends BaseSavedState {
        CharSequence onText;
        CharSequence offText;

        SavedState(Parcelable superState) {
            super(superState);
        }

        private SavedState(Parcel in) {
            super(in);
            onText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
            offText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
        }

        @Override
        public void writeToParcel(Parcel out, int flags) {
            super.writeToParcel(out, flags);
            TextUtils.writeToParcel(onText, out, flags);
            TextUtils.writeToParcel(offText, out, flags);
        }

        public static final Creator<SavedState> CREATOR
                = new Creator<SavedState>() {

            @Override
            public SavedState createFromParcel(Parcel in) {
                return new SavedState(in);
            }

            @Override
            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };
    }
}